import utility
import streams

type
  NbtKind* = enum
    nbtEnd = 0
    nbtByte = 1
    nbtShort = 2
    nbtInt = 3
    nbtLong = 4
    nbtFloat = 5
    nbtDouble = 6
    nbtByteArray = 7
    nbtString = 8
    nbtList = 9
    nbtCompound = 10
    nbtIntArray = 11
    nbtLongArray = 12
  Nbt* = ref object
    name*: string
    case kind*: NbtKind
    of nbtEnd:
      discard
    of nbtByte:
      byteValue*: int8
    of nbtShort:
      shortValue*: int16
    of nbtInt:
      intValue*: int32
    of nbtLong:
      longValue*: int64
    of nbtFloat:
      floatValue*: float32
    of nbtDouble:
      doubleValue*: float64
    of nbtByteArray:
      byteArrayValue*: seq[int8]
    of nbtString:
      stringValue*: string
    of nbtList:
      listKind: NbtKind
      listValue*: seq[Nbt]
    of nbtCompound:
      compoundValue*: seq[Nbt]
    of nbtIntArray:
      intArrayValue*: seq[int32]
    of nbtLongArray:
      longArrayValue*: seq[int64]

proc writeNbt*(stream: Stream, nbt: Nbt, payloadOnly = false) =
  if not payloadOnly:
    stream.write nbt.kind.int8
    if nbt.kind != nbtEnd:
      stream.write nbt.name.len.uint16.toBigEndian
      stream.write nbt.name
  case nbt.kind
  of nbtEnd:
    discard
  of nbtByte:
    stream.write nbt.byteValue
  of nbtShort:
    stream.write nbt.shortValue.toBigEndian
  of nbtInt:
    stream.write nbt.intValue.toBigEndian
  of nbtLong:
    stream.write nbt.longValue.toBigEndian
  of nbtFloat:
    stream.write nbt.floatValue.toBigEndian
  of nbtDouble:
    stream.write nbt.doubleValue.toBigEndian
  of nbtByteArray:
    stream.write nbt.byteArrayValue.len.int32.toBigEndian
    for byteValue in nbt.byteArrayValue:
      stream.write byteValue
  of nbtString:
    stream.write nbt.stringValue.len.int16.toBigEndian
    stream.write nbt.stringValue
  of nbtList:
    stream.write nbt.listKind.int8
    stream.write nbt.listValue.len.int32.toBigEndian
    for innerNbt in nbt.listValue:
      stream.writeNbt innerNbt, true
  of nbtCompound:
    for innerNbt in nbt.compoundValue:
      stream.writeNbt innerNbt
    stream.writeNbt Nbt(kind: nbtEnd)
  of nbtIntArray:
    stream.write nbt.intArrayValue.len.int32.toBigEndian
    for intValue in nbt.intArrayValue:
      stream.write intValue.toBigEndian
  of nbtLongArray:
    stream.write nbt.intArrayValue.len.int32.toBigEndian
    for longValue in nbt.longArrayValue:
      stream.write longValue.toBigEndian

when isMainModule:
  let
    fortyTwo = Nbt(kind: nbtInt, name: "FortyTwo", intValue: 42)
    sixtyNine = Nbt(kind: nbtInt, name: "SixtyNine", intValue: 69)
    pi = Nbt(kind: nbtDouble, name: "Pi", doubleValue: 3.141592653589793238)
    fibs = Nbt(kind: nbtByteArray, name: "Fibs", byteArrayValue: @[0'i8, 1'i8, 1'i8, 2'i8, 3'i8, 5'i8, 8'i8, 13'i8, 21'i8, 34'i8, 55'i8, 89'i8])
    ints = Nbt(kind: nbtList, name: "Ints", listKind: nbtInt, listValue: @[fortyTwo, sixtyNine])
    nbt = Nbt(kind: nbtCompound, name: "Nbt", compoundValue: @[pi, fibs, ints])
  var stream = newStringStream()
  stream.writeNbt nbt
  stream.flush
  stream.setPosition 0
  for streamByte in stream.readAll:
    echo streamByte.ord
